home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / GameKit / Examples / PacMan / Monster.m < prev    next >
Text File  |  1995-06-12  |  10KB  |  350 lines

  1.  
  2. #import <libc.h>
  3. #import "Monster.h"
  4. #import "PacManGameBrain.h"
  5. #import "Player.h"
  6. #import "Maze.h"
  7. #import <appkit/appkit.h>
  8.  
  9. // Get defs of static data arrays which control movement choices
  10. #import "MonsterMovement.h"
  11.  
  12. @implementation Monster
  13.  
  14. - init
  15. {
  16.     return [self initGhost:0 player:nil maze:nil at:0 :0];
  17. }
  18.  
  19. - initGhost:(int)num player:(id)pac maze:(id)world at:(int)sx :(int)sy
  20. {    // initialize all instance vars
  21.     [super init];
  22.     ghosts[1] = [NXImage findImageNamed:"Ghosts.tiff"];
  23.     ghosts[2] = [NXImage findImageNamed:"GhostsBig.tiff"];
  24.     lastx = - 4 * GHOST_SIZE;
  25.     lasty = - 4 * GHOST_SIZE;
  26.     player = pac;
  27.     maze = world;
  28.     eatable = NO;
  29.     state = HOVER;
  30.     myX = sx * GHOST_SIZE; myY = sy * GHOST_SIZE;
  31.     myColor = num;
  32.     px = 2; py = 0;
  33.     loops = 0;
  34.     fix = NO;
  35.     
  36.     return self;
  37. }
  38.  
  39. - (BOOL)canBeEaten        // returns "eatable".
  40. { return eatable; }
  41.  
  42. - move:sender        // move one frame
  43. {
  44.     // use current state to decide which type of movement to do
  45.     switch (state) {
  46.         case HOVER : {
  47.             [self hover];
  48.             break;
  49.         }
  50.         case CHASE : {
  51.             [self chase];
  52.             break;
  53.         }
  54.         case RUN   : {
  55.             [self runAway];
  56.             break;
  57.         }
  58.         case HOME  : {
  59.             [self goHome];
  60.             break;
  61.         }
  62.         default    : {    // no movement, since illegal state
  63.             // (we should _never_ get here!)
  64.             px = 0; py = 0;
  65.             break;
  66.         }
  67.     }
  68.     return self;
  69. }
  70.  
  71. - hover                // hover moves solid ghost when in ghost chamber
  72. {
  73.     int door_x, door_y;
  74.     int k;
  75.     
  76.     [maze doorPosition:&door_x :&door_y];
  77.  
  78.     if (myX == door_x) {
  79.         if (myY >= (door_y + GHOST_SIZE)) {
  80. // The ghost is now completely outside the box; we will
  81. // change its state so that it follows the player around
  82. // (or runs away if power dot was eaten)
  83.             if (eatable) {
  84.                 state = RUN;
  85.                 [self runAway];
  86.             } else {
  87.                 state = CHASE;
  88.                 [self chase];
  89.             }
  90.             if (myY == (door_y + GHOST_SIZE - 1)) py = 1;
  91.             if (myY == (door_y + GHOST_SIZE + 1)) py = -1;
  92.             return self;
  93.         } else if (myY >= (door_y - 1 - GHOST_SIZE)) {
  94. // The ghost is directly underneath the door to the outside.
  95. // Send it out if more than 3 loops made, or 50% chance to leave if
  96. // at least one loop is complete.
  97.             if ((++loops) > 1) {
  98.                 if ((loops > 3) || ((random() & 0x0f) > 7) || eatable) {
  99.                     if (eatable) py = 1; // keep right speed
  100.                     else py = 2;
  101.                     px = 0;
  102.                     return self;
  103.     }    }    }    }
  104. // The rest of the method drives the ghost around the
  105. // box in a counterclockwise pattern.
  106.     k = 0;
  107.     if (px > 0) {
  108.         if ([maze monsterWall:(myX + 1 + GHOST_SIZE) :myY])
  109.             { k = 1; px = 0; py = 2; }
  110.     } else if (px < 0) {
  111.         if ([maze monsterWall:(myX - 2) :myY])
  112.             { k = 1; px = 0; py = -2; }
  113.     } else if (py > 0) {
  114.         if ([maze monsterWall:myX :(myY + 1 + GHOST_SIZE)])
  115.             { k = 1; px = -2; py = 0; }
  116.     } else if ([maze monsterWall:myX :(myY - 2)])
  117.             { k = 1; px = 2; py = 0; }
  118.     if (eatable && k) {
  119.         px /= 2;
  120.         py /= 2;
  121.     }
  122.     if ((abs(py) == 2) && (myY & 0x1)) fix = YES;    // keep synced
  123.     if ((abs(px) == 2) && (myX & 0x1)) fix = YES;    // keep synced
  124.     return self;
  125. }
  126.  
  127. - runAway            // ghost runs away at 1/2 speed after power pill
  128. {
  129.     int pac_x = [player xpos];
  130.     int pac_y = [player ypos];
  131.     register int tdir = 0x0f, sense;
  132.     int dx = myX % GHOST_SIZE; int dy = myY % GHOST_SIZE;
  133.     
  134.  
  135.     if (dx) tdir &= 0x03; // only allow l/r to sync us up
  136.     if (dy) tdir &= 0x0c; // only allow u/d to sync us up
  137. // first, find the directions in which this ghost can go
  138.     if ([maze playerWall:(myX+GHOST_SIZE) :myY]    || (px < 0)) tdir &= ~0x01;
  139.     if ([maze playerWall:(myX-1) :myY]             || (px > 0)) tdir &= ~0x02;
  140.     if ([maze playerWall:myX     :(myY-1)]         || (py > 0)) tdir &= ~0x04;
  141.     if ([maze playerWall:myX :(myY+GHOST_SIZE)]    || (py < 0)) tdir &= ~0x08;
  142.  
  143. // now choose the new direction for the ghost
  144.     if ((random() & 0x0f) > 4)
  145.         sense = find[sgn(pac_y - myY) + 1][sgn(pac_x - myX) + 1];
  146.     else sense = random() & 0x07;
  147.     px = rxvec[tdir][sense];
  148.     py = ryvec[tdir][sense];
  149.     return self;
  150. }
  151.  
  152. - chase                // ghost chases player down
  153. {
  154.     int pac_x = [player xpos];
  155.     int pac_y = [player ypos];
  156.     register int tdir = 0x0f;
  157.     long rnum = random() & 0x3FF;
  158.     register BOOL noReverse = (rnum != 1);
  159.     register int sense, curPath;
  160.     int dx = myX % GHOST_SIZE; int dy = myY % GHOST_SIZE;
  161.  
  162. // sync position to block boundaries
  163.     if (px < 0) {
  164.       if (dx > 1) return self;
  165.       if (dx == 1) { px = -1; return self; }
  166.     } else if (px > 0) {
  167.       if ((dx < GHOST_SIZE - 1) && dx) return self;
  168.       if (dx == GHOST_SIZE - 1) { px = 1; return self; }
  169.     } else if (py < 0) {
  170.       if (dy > 1) return self;
  171.       if (dy == 1) { py = -1; return self; }
  172.     } else if (py > 0) {
  173.       if ((dy < GHOST_SIZE - 1) && dy) return self;
  174.       if (dy == GHOST_SIZE - 1) { py = 1; return self; }
  175.     }
  176.  
  177. // determine where the pac is at in relation to us.
  178.     curPath = find[sgn(pac_y - myY) + 1][sgn(pac_x - myX) + 1];
  179.     // we don't use random sense if ghost can reverse,
  180.     // since it ends up looking dumb
  181.     if (((random() & 0x0f) > 4) || !noReverse) // 75% of time it's algorithmic
  182.         sense = curPath;
  183.     else sense = random() & 0x07;
  184.  
  185. // first, find the directions in which this ghost can go
  186.     if ([maze playerWall:(myX+2+GHOST_SIZE) :myY]    || ((px < 0) && noReverse))
  187.         tdir &= ~0x01;
  188.     if ([maze playerWall:(myX-2) :myY]                || ((px > 0) && noReverse))
  189.         tdir &= ~0x02;
  190.     if ([maze playerWall:myX     :(myY-2)]            || ((py > 0) && noReverse))
  191.         tdir &= ~0x04;
  192.     if ([maze playerWall:myX :(myY+2+GHOST_SIZE)]    || ((py < 0) && noReverse))
  193.         tdir &= ~0x08;
  194.  
  195. // now choose the new direction for the ghost
  196.     px = fxvec[tdir][sense];
  197.     py = fyvec[tdir][sense];
  198.     return self;
  199. }
  200.  
  201. - goHome            // used to make the eyes run to ghost chamber at 2x speed
  202. {
  203.     int door_x, door_y;
  204.     register int tdir = 0x0f, sense;
  205.     int dx = myX % GHOST_SIZE; int dy = myY % GHOST_SIZE;
  206.  
  207.     [maze doorPosition:&door_x :&door_y];
  208.     // there, we'll "catch" it and suck it into the box.
  209.     if (myX == door_x) { // using GHOST_SIZE * 2 moves it to bottom of box
  210.         if ((myY >= door_y - GHOST_SIZE * 2) && (myY <= door_y + GHOST_SIZE)) {
  211.             if ((myY >= door_y - GHOST_SIZE) && (myY <= door_y + GHOST_SIZE)) {
  212. // The ghost is right above the door to the ghost box.
  213. // We'll send it down into the box.  We're assuming
  214. // here that the ghost box is below the door.
  215. // If not, the results will be unpredictable.
  216.                 px = 0;
  217.                 if (dy && (dy < 4)) py = -dy; // sync to blocks
  218.                 else py = -4;
  219.             } else if (myY == (door_y - GHOST_SIZE * 2)) {
  220. // The ghost is all the way inside the box.  Here it'll
  221. // be "reborn" -- its state will be changed to that of a
  222. // solid ghost hovering inside the ghost box.
  223.                 state = HOVER;
  224.                 loops = 0;
  225.                 eatable = NO;
  226.                 px = 2;
  227.                 py = 0;
  228.             }
  229.             return self;
  230.     }    }
  231.  
  232.     door_y += GHOST_SIZE;    // seek the block above the door; when ghost gets
  233. // now, sync ghost to maze blocks
  234.     if (px < 0) {
  235.       if (dx > 3) return self;
  236.       if (dx) { px = -dx; return self; }
  237.     } else if (px > 0) {
  238.       if ((dx < GHOST_SIZE - 3) && dx) return self;
  239.       else if (dx) { px = GHOST_SIZE - dx; return self; }
  240.     } else if (py < 0) {
  241.       if (dy > 3) return self;
  242.       if (dy) { py = -dy; return self; }
  243.     } else if (py > 0) {
  244.       if ((dy < GHOST_SIZE - 3) && dy) return self;
  245.       else if (dy) { py = GHOST_SIZE - dy; return self; }
  246.     }
  247.  
  248.     if (dx) tdir &= 0x03;
  249.     if (dy) tdir &= 0x0c;
  250.     
  251. // first, find the directions in which this ghost can go
  252.     if ([maze monsterWall:(myX+4+GHOST_SIZE) :myY]    || (px < 0)) tdir &= ~0x01;
  253.     if ([maze monsterWall:(myX-4) :myY]                || (px > 0)) tdir &= ~0x02;
  254.     if ([maze monsterWall:myX     :(myY-4)]            || (py > 0)) tdir &= ~0x04;
  255.     if ([maze monsterWall:myX :(myY+4+GHOST_SIZE)]    || (py < 0)) tdir &= ~0x08;
  256.  
  257. // now choose the new direction for the ghost--not random!
  258.     sense = find[sgn(door_y - myY) + 1][sgn(door_x - myX) + 1];
  259.     px = pxvec[tdir][sense];
  260.     py = pyvec[tdir][sense];
  261.     return self;
  262. }
  263.  
  264. - powerDot:(BOOL)eat    // called when power dots are eaten or run out.
  265. {
  266.     if (state != HOME) eatable = eat;
  267.     if ((state == CHASE) && (eat == YES)) {
  268.         state = RUN;
  269.         px  = 0; py = 0;    // allows quick reverse of direction
  270.         [self runAway];
  271.     }
  272.     if ((state == RUN)   && (eat ==  NO)) {
  273.         state = CHASE;
  274.         if (abs(px) < 2) px *= 2; // ifs should actually be unnecessary...
  275.         if (abs(py) < 2) py *= 2;
  276.         [self chase];
  277.     }
  278.     if (state == HOVER) {    // speed up or slow down hovering ghost
  279.         // has to happen here, since hover leaves it alone until it hits
  280.         // an edge of the box...
  281.         if (eat) {
  282.             if (abs(px) == 2) px /=2;
  283.             if (abs(py) == 2) py /= 2;
  284.         } else {
  285.             if (abs(px) == 1) px *=2;
  286.             if (abs(py) == 1) py *= 2;
  287.     }    }
  288.     if (eat) {
  289.         powerState = DOT_EATEN;
  290.         powerTime = 0;
  291.     } else  powerState = DOT_NONE;
  292.     return self;
  293. }
  294.  
  295. - (int)munch            // called when pac intersects ghost
  296. {
  297.     if (state == HOME) return HARMLESS;
  298.     if (!eatable) return NO;
  299.     px  = 0; py  = 0;    // allows reverse of direction
  300.     state = HOME;
  301.     [self goHome];
  302.     eatable = NO;
  303.     return YES;
  304. }
  305.  
  306. - moveOneFrame
  307. {
  308.     [super moveOneFrame];        // move the ghost
  309.     if (fix) {
  310.         myY -= sgn(py);    // fix for when hovering -- to re-sync
  311.         myX -= sgn(px);
  312.         fix = 0;
  313.     }
  314.     return self;
  315. }
  316.  
  317. - renderAt:(int)posx :(int)posy move:(BOOL)moveOk    // draw ghost
  318. {
  319.     int col = myColor & 0x3;
  320.     NXRect from;
  321.     NXPoint pos;
  322.     
  323.     [super renderAt:posx :posy move:moveOk];
  324.  
  325.     // decide to draw invisible or eye ghosts if necessary
  326.     if (eatable && ((powerState != DOT_FADING) || (img & 0x4)))
  327.         col = INVISIBLE_GHOST;
  328.     else if (state == HOME) col = GHOST_EYES;
  329.     NXSetRect(&from,
  330.         ((++img >> 1) & 0x3) * GHOST_SIZE * scale,    col * GHOST_SIZE * scale,
  331.         GHOST_SIZE * scale, GHOST_SIZE * scale);
  332.     pos.x = myX * scale + posx; pos.y = myY * scale + posy;
  333.     [ghosts[scale] composite:NX_SOVER fromRect:&from toPoint:&pos];
  334.     [self powerCount];    // count down time till power dot wears off
  335.     return self;
  336. }
  337.  
  338. - powerCount
  339. {
  340.     if (![[NXApp delegate] paused]) {
  341.         if (((++powerTime) >> 4) > flash_ticks[[[NXApp delegate] level]])
  342.             powerState = DOT_FADING;
  343.         if ((powerTime >> 4) > off_ticks[[[NXApp delegate] level]])
  344.             [self powerDot:NO];
  345.     }
  346.     return self;
  347. }
  348.  
  349. @end
  350.